/* Charles Douglas
 * CSCE 4230
 */

#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>

GLdouble ctrlpoints[7][3] = {
	{-4.0, -4.0, 0.0}, { -3.0, -1.0, 0.0}, 
	{-1.0, -3.0, 0.0}, {0.0, 0.0, 0.0}, 
    {1.0, 3.0, 0.0}, {3.0, 1.0, 0.0}, {4.0, 4.0, 0.0}};

/* approximate mouse tolerance in pixels */
GLdouble mouse_tolerance = 3.0;
int selected_point = -1;



/* returns TRUE if the three points are colinear in the x/y plane */
int colinear(GLdouble* p1, GLdouble* p2, GLdouble* p3)
{
   /* true if (y3-y2)/(x3-x2) == (y2-y1) / (x2 - x1) */
   return (((p3[1] - p2[1]) / (p3[0] - p2[0])) == ((p2[1] - p1[1]) /(p2[0] - p1[0])));
}

int find_selected_point(GLdouble x, GLdouble y)
{
   int index = -1;
   GLdouble winx;
   GLdouble winy;
   GLdouble winz;
   int i;
   GLdouble model_view[16];
   GLdouble projection[16];
   GLint viewport[4];

   glGetIntegerv(GL_VIEWPORT, viewport);
   glGetDoublev(GL_MODELVIEW_MATRIX, model_view);
   glGetDoublev(GL_PROJECTION_MATRIX, projection);
   
   for (i=0; i < 7; ++i)
   {


      gluProject((GLdouble) ctrlpoints[i][0], 
                 (GLdouble) ctrlpoints[i][1], 
                 (GLdouble) ctrlpoints[i][2], 
		      model_view, projection, viewport, 
		      &winx, &winy, &winz);

      if (fabs(
             sqrt(
               (pow((winx - x), 2) + pow((winy - y), 2)))) <= mouse_tolerance)
      {
         index=i;
      }

   }

   return index;
}

void move_selected_point(GLdouble winx, GLdouble winy)
{
   GLdouble x;
   GLdouble y;
   GLdouble z;
   GLdouble deltax;
   GLdouble deltay;
   GLdouble winz = 0.5;
   GLdouble model_view[16];
   GLdouble projection[16];
   GLint viewport[4];

   if (selected_point != -1)
   {
       glGetIntegerv(GL_VIEWPORT, viewport);
       glGetDoublev(GL_MODELVIEW_MATRIX, model_view);
       glGetDoublev(GL_PROJECTION_MATRIX, projection);

       gluUnProject(winx, winy, winz, 
                    model_view, projection, viewport,
                    &x, &y, &z);

       deltax = x - ctrlpoints[selected_point][0];
       deltay = y - ctrlpoints[selected_point][1];
       
       ctrlpoints[selected_point][0] = x;
       ctrlpoints[selected_point][1] = y;

       /* if selected point is 2,  3, or 4 we need to make sure they stay
          colinear */
       switch(selected_point){
            case (2):
                ctrlpoints[4][0] = ctrlpoints[4][0] + -1*deltax;
                ctrlpoints[4][1] = ctrlpoints[4][1] + -1*deltay;
            break;
            case (3):
                ctrlpoints[2][0] = ctrlpoints[2][0] + deltax;
                ctrlpoints[2][1] = ctrlpoints[2][1] + deltay;
                ctrlpoints[4][0] = ctrlpoints[4][0] + deltax;
                ctrlpoints[4][1] = ctrlpoints[4][1] + deltay;
            break;
            case (4):
                ctrlpoints[2][0] = ctrlpoints[2][0] + -1*deltax;
                ctrlpoints[2][1] = ctrlpoints[2][1] + -1*deltay;
            break;
        }
   }

}

void init(void)
{
   glClearColor(0.0, 0.0, 0.0, 0.0);
   glShadeModel(GL_FLAT);
   glEnable(GL_MAP1_VERTEX_3);
}

void display(void)
{
   int i;



   glClear(GL_COLOR_BUFFER_BIT);

   glMap1d(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[0][0]);
   glColor3f(1.0, 1.0, 1.0);
   glBegin(GL_LINE_STRIP);
      for (i = 0; i <= 30; i++) 
         glEvalCoord1f((GLdouble) i/30.0);
   glEnd(); 

   glMap1d(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &ctrlpoints[3][0]);
   glBegin(GL_LINE_STRIP);
      for (i = 0; i <= 30; i++) 
         glEvalCoord1f((GLdouble) i/30.0);
   glEnd(); 
   /* The following code displays the control points as dots. */
   glPointSize(5.0);
   glColor3f(1.0, 1.0, 0.0);
   glBegin(GL_POINTS);
      for (i = 0; i < 7; i++) 
         glVertex3dv(&ctrlpoints[i][0]);
   glEnd();
   glFlush();

}

void reshape(int w, int h)
{
   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   if (w <= h)
      glOrtho(-5.0, 5.0, -5.0*(GLdouble)h/(GLdouble)w, 
               5.0*(GLdouble)h/(GLdouble)w, -5.0, 5.0);
   else
      glOrtho(-5.0*(GLdouble)w/(GLdouble)h, 
               5.0*(GLdouble)w/(GLdouble)h, -5.0, 5.0, -5.0, 5.0);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}

void keyboard(unsigned char key, int x, int y)
{
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}

void mouse(int button, int state, int x, int y)
{
   x = (GLdouble) x;
   y = (GLdouble) (glutGet(GLUT_WINDOW_HEIGHT) - y);  

   switch(button) {
      case (GLUT_LEFT_BUTTON):
         switch(state) {
            case (GLUT_DOWN):
               if (selected_point == -1)
                  selected_point = find_selected_point(x, y);

            break;
            case (GLUT_UP):
               selected_point = -1;
               glutPostRedisplay();
            break;
         }
      break;
   }
}

void mousemove(int x, int y)
{
   x = (GLdouble) x;
   y = (GLdouble) (glutGet(GLUT_WINDOW_HEIGHT) - y);
  
   if (selected_point != -1)
   {
      move_selected_point(x, y);
      glutPostRedisplay();
   }   
}

int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize (500, 500);
   glutInitWindowPosition (100, 100);
   glutCreateWindow (argv[0]);
   init ();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc (keyboard);
   glutMouseFunc(mouse);
   glutMotionFunc(mousemove);
   glutMainLoop();
   return 0;
}
